package etcd

import (
	"context"
	"encoding/json"
	"fmt"
	etcdV3 "go.etcd.io/etcd/client/v3"
	"log"
	"time"
)

type Option func(o *options)

type options struct {
	ctx       context.Context
	namespace string
	ttl       time.Duration
	maxRetry  int
}

func Context(ctx context.Context) Option {
	return func(o *options) {
		o.ctx = ctx
	}
}

func Namespace(ns string) Option {
	return func(o *options) {
		o.namespace = ns
	}
}

func TTL(ttl time.Duration) Option {
	return func(o *options) {
		o.ttl = ttl
	}
}

func MaxRetry(retry int) Option {
	return func(o *options) {
		o.maxRetry = retry
	}
}

type Registry struct {
	opts   *options
	client *etcdV3.Client
	kv     etcdV3.KV
	lease  etcdV3.Lease
}

func NewEtcdRegister(etcdCli *etcdV3.Client, opts ...Option) *Registry {
	defaultOpt := &options{
		ctx:       context.Background(),
		ttl:       time.Second * 15,
		namespace: "/services",
		maxRetry:  3,
	}
	for _, o := range opts {
		o(defaultOpt)
	}
	return &Registry{
		opts:   defaultOpt,
		client: etcdCli,
		kv:     etcdV3.NewKV(etcdCli),
	}
}

func (r *Registry) Register(ctx context.Context, instance *ServiceInstance) error {
	key := fmt.Sprintf("%s/%s/%s", r.opts.namespace, instance.Name, instance.ID)
	value, err := json.Marshal(instance)
	if err != nil {
		return err
	}
	valueStr := string(value)
	if r.lease != nil {
		_ = r.lease.Close()
	}
	r.lease = etcdV3.NewLease(r.client)
	leaseID, err := r.registerWithKV(ctx, key, valueStr)
	if err != nil {
		return err
	}
	fmt.Printf("服务【%s】 注册成功!!!! \n", instance.Name)
	fmt.Printf("注册信息: %s \n", valueStr)
	go r.heartBet(r.opts.ctx, leaseID, key, valueStr)
	return nil
}

func (r *Registry) Unregister(ctx context.Context, service *ServiceInstance) error {
	defer func() {
		if r.lease != nil {
			_ = r.lease.Close()
			r.lease = nil
		}
	}()
	key := fmt.Sprintf("%s/%s/%s", r.opts.namespace, service.Name, service.ID)
	_, err := r.client.Delete(ctx, key)
	fmt.Printf("服务:【%s】 下线成功 \n", service.Name)
	return err
}

func (r *Registry) GetService(ctx context.Context, name string) ([]*ServiceInstance, error) {
	key := fmt.Sprintf("%s/%s", r.opts.namespace, name)
	resp, err := r.kv.Get(ctx, key, etcdV3.WithPrefix())
	if err != nil {
		return nil, err
	}
	items := make([]*ServiceInstance, 0, len(resp.Kvs))
	for _, kv := range resp.Kvs {
		instance := new(ServiceInstance)
		err = json.Unmarshal(kv.Value, instance)
		if err != nil {
			return nil, err
		}
		if instance.Name != name {
			continue
		}
		items = append(items, instance)
	}
	return items, nil
}

func (r *Registry) Watch(ctx context.Context, name string) (*Watcher, error) {
	key := fmt.Sprintf("%s/%s", r.opts.namespace, name)
	return newWatcher(ctx, key, name, r.client)
}

func (r *Registry) registerWithKV(ctx context.Context, key string, value string) (etcdV3.LeaseID, error) {
	grant, err := r.lease.Grant(ctx, int64(r.opts.ttl.Seconds()))
	if err != nil {
		return 0, err
	}
	_, err = r.client.Put(ctx, key, value, etcdV3.WithLease(grant.ID))
	if err != nil {
		return 0, err
	}
	return grant.ID, nil
}

// heartBet TODO 重试机制
func (r *Registry) heartBet(ctx context.Context, leaseID etcdV3.LeaseID, key string, value string) {
	kac, err := r.client.KeepAlive(ctx, leaseID)
	if err != nil {
		log.Println(err)
	}
	for {
		select {
		case _, ok := <-kac:
			if !ok {
				log.Println("心跳断开")
				return
			}
		case <-r.opts.ctx.Done():
			return
		}
	}
}
